using ICSharpCode.SharpZipLib.Checksums;
using ICSharpCode.SharpZipLib.Zip.Compression;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using System;
using System.Collections.Generic;
using System.IO;
namespace ICSharpCode.SharpZipLib.Zip
{
	public class ZipOutputStream : DeflaterOutputStream
	{
		private List<ZipEntry> entries = new List<ZipEntry>();
		private Crc32 crc32_0 = new Crc32();
		private ZipEntry curEntry;
		private int defaultCompressionLevel = -1;
		private CompressionMethod curMethod = CompressionMethod.Deflated;
		private long size;
		private long offset;
		private byte[] zipComment = new byte[0];
		private bool patchEntryHeader;
		private long crcPatchPos = -1L;
		private long sizePatchPos = -1L;
		private UseZip64 useZip64_ = UseZip64.Dynamic;
		public bool IsFinished
		{
			get
			{
				return this.entries == null;
			}
		}
		public UseZip64 UseZip64
		{
			get
			{
				return this.useZip64_;
			}
			set
			{
				this.useZip64_ = value;
			}
		}
		public ZipOutputStream(Stream baseOutputStream) : base(baseOutputStream, new Deflater(-1, true))
		{
		}
		public ZipOutputStream(Stream baseOutputStream, int bufferSize) : base(baseOutputStream, new Deflater(-1, true), bufferSize)
		{
		}
		public void SetComment(string comment)
		{
			byte[] array = ZipConstants.ConvertToArray(comment);
			if (array.Length > 65535)
			{
				throw new ArgumentOutOfRangeException("comment");
			}
			this.zipComment = array;
		}
		public void SetLevel(int level)
		{
			this.deflater_.SetLevel(level);
			this.defaultCompressionLevel = level;
		}
		public int GetLevel()
		{
			return this.deflater_.GetLevel();
		}
		private void WriteLeShort(int value)
		{
			this.baseOutputStream_.WriteByte((byte)(value & 255));
			this.baseOutputStream_.WriteByte((byte)(value >> 8 & 255));
		}
		private void WriteLeInt(int value)
		{
			this.WriteLeShort(value);
			this.WriteLeShort(value >> 16);
		}
		private void WriteLeLong(long value)
		{
			this.WriteLeInt((int)value);
			this.WriteLeInt((int)(value >> 32));
		}
		public void PutNextEntry(ZipEntry entry)
		{
			if (entry == null)
			{
				throw new ArgumentNullException("entry");
			}
			if (this.entries == null)
			{
				throw new InvalidOperationException("ZipOutputStream was finished");
			}
			if (this.curEntry != null)
			{
				this.CloseEntry();
			}
			if (this.entries.get_Count() == 2147483647)
			{
				throw new ZipException("Too many entries for Zip file");
			}
			CompressionMethod compressionMethod = entry.CompressionMethod;
			int level = this.defaultCompressionLevel;
			entry.Flags &= 2048;
			this.patchEntryHeader = false;
			bool flag;
			if (entry.Size == 0L)
			{
				entry.CompressedSize = entry.Size;
				entry.Crc = 0L;
				compressionMethod = CompressionMethod.Stored;
				flag = true;
			}
			else
			{
				flag = (entry.Size >= 0L && entry.HasCrc);
				if (compressionMethod == CompressionMethod.Stored)
				{
					if (!flag)
					{
						if (!base.CanPatchEntries)
						{
							compressionMethod = CompressionMethod.Deflated;
							level = 0;
						}
					}
					else
					{
						entry.CompressedSize = entry.Size;
						flag = entry.HasCrc;
					}
				}
			}
			if (!flag)
			{
				if (!base.CanPatchEntries)
				{
					entry.Flags |= 8;
				}
				else
				{
					this.patchEntryHeader = true;
				}
			}
			if (base.Password != null)
			{
				entry.IsCrypted = true;
				if (entry.Crc < 0L)
				{
					entry.Flags |= 8;
				}
			}
			entry.Offset = this.offset;
			entry.CompressionMethod = compressionMethod;
			this.curMethod = compressionMethod;
			this.sizePatchPos = -1L;
			if (this.useZip64_ == UseZip64.On || (entry.Size < 0L && this.useZip64_ == UseZip64.Dynamic))
			{
				entry.ForceZip64();
			}
			this.WriteLeInt(67324752);
			this.WriteLeShort(entry.Version);
			this.WriteLeShort(entry.Flags);
			this.WriteLeShort((int)((byte)entry.CompressionMethodForHeader));
			this.WriteLeInt((int)entry.DosTime);
			if (flag)
			{
				this.WriteLeInt((int)entry.Crc);
				if (entry.LocalHeaderRequiresZip64)
				{
					this.WriteLeInt(-1);
					this.WriteLeInt(-1);
				}
				else
				{
					this.WriteLeInt(entry.IsCrypted ? ((int)entry.CompressedSize + 12) : ((int)entry.CompressedSize));
					this.WriteLeInt((int)entry.Size);
				}
			}
			else
			{
				if (this.patchEntryHeader)
				{
					this.crcPatchPos = this.baseOutputStream_.get_Position();
				}
				this.WriteLeInt(0);
				if (this.patchEntryHeader)
				{
					this.sizePatchPos = this.baseOutputStream_.get_Position();
				}
				if (entry.LocalHeaderRequiresZip64 || this.patchEntryHeader)
				{
					this.WriteLeInt(-1);
					this.WriteLeInt(-1);
				}
				else
				{
					this.WriteLeInt(0);
					this.WriteLeInt(0);
				}
			}
			byte[] array = ZipConstants.ConvertToArray(entry.Flags, entry.Name);
			if (array.Length > 65535)
			{
				throw new ZipException("Entry name too long.");
			}
			ZipExtraData zipExtraData = new ZipExtraData(entry.ExtraData);
			if (entry.LocalHeaderRequiresZip64)
			{
				zipExtraData.StartNewEntry();
				if (flag)
				{
					zipExtraData.AddLeLong(entry.Size);
					zipExtraData.AddLeLong(entry.CompressedSize);
				}
				else
				{
					zipExtraData.AddLeLong(-1L);
					zipExtraData.AddLeLong(-1L);
				}
				zipExtraData.AddNewEntry(1);
				if (!zipExtraData.Find(1))
				{
					throw new ZipException("Internal error cant find extra data");
				}
				if (this.patchEntryHeader)
				{
					this.sizePatchPos = (long)zipExtraData.CurrentReadIndex;
				}
			}
			else
			{
				zipExtraData.Delete(1);
			}
			byte[] entryData = zipExtraData.GetEntryData();
			this.WriteLeShort(array.Length);
			this.WriteLeShort(entryData.Length);
			if (array.Length > 0)
			{
				this.baseOutputStream_.Write(array, 0, array.Length);
			}
			if (entry.LocalHeaderRequiresZip64 && this.patchEntryHeader)
			{
				this.sizePatchPos += this.baseOutputStream_.get_Position();
			}
			if (entryData.Length > 0)
			{
				this.baseOutputStream_.Write(entryData, 0, entryData.Length);
			}
			this.offset += (long)(30 + array.Length + entryData.Length);
			if (entry.AESKeySize > 0)
			{
				this.offset += (long)entry.AESOverheadSize;
			}
			this.curEntry = entry;
			this.crc32_0.Reset();
			if (compressionMethod == CompressionMethod.Deflated)
			{
				this.deflater_.Reset();
				this.deflater_.SetLevel(level);
			}
			this.size = 0L;
			if (entry.IsCrypted)
			{
				if (entry.Crc < 0L)
				{
					this.WriteEncryptionHeader(entry.DosTime << 16);
				}
				else
				{
					this.WriteEncryptionHeader(entry.Crc);
				}
			}
		}
		public void CloseEntry()
		{
			if (this.curEntry == null)
			{
				throw new InvalidOperationException("No open entry");
			}
			long totalOut = this.size;
			if (this.curMethod == CompressionMethod.Deflated)
			{
				if (this.size >= 0L)
				{
					base.Finish();
					totalOut = this.deflater_.TotalOut;
				}
				else
				{
					this.deflater_.Reset();
				}
			}
			if (this.curEntry.AESKeySize > 0)
			{
				this.baseOutputStream_.Write(this.AESAuthCode, 0, 10);
			}
			if (this.curEntry.Size < 0L)
			{
				this.curEntry.Size = this.size;
			}
			else
			{
				if (this.curEntry.Size != this.size)
				{
					throw new ZipException(string.Concat(new object[]
					{
						"size was ",
						this.size,
						", but I expected ",
						this.curEntry.Size
					}));
				}
			}
			if (this.curEntry.CompressedSize < 0L)
			{
				this.curEntry.CompressedSize = totalOut;
			}
			else
			{
				if (this.curEntry.CompressedSize != totalOut)
				{
					throw new ZipException(string.Concat(new object[]
					{
						"compressed size was ",
						totalOut,
						", but I expected ",
						this.curEntry.CompressedSize
					}));
				}
			}
			if (this.curEntry.Crc < 0L)
			{
				this.curEntry.Crc = this.crc32_0.Value;
			}
			else
			{
				if (this.curEntry.Crc != this.crc32_0.Value)
				{
					throw new ZipException(string.Concat(new object[]
					{
						"crc was ",
						this.crc32_0.Value,
						", but I expected ",
						this.curEntry.Crc
					}));
				}
			}
			this.offset += totalOut;
			if (this.curEntry.IsCrypted)
			{
				if (this.curEntry.AESKeySize > 0)
				{
					this.curEntry.CompressedSize += (long)this.curEntry.AESOverheadSize;
				}
				else
				{
					this.curEntry.CompressedSize += 12L;
				}
			}
			if (this.patchEntryHeader)
			{
				this.patchEntryHeader = false;
				long position = this.baseOutputStream_.get_Position();
				this.baseOutputStream_.Seek(this.crcPatchPos, 0);
				this.WriteLeInt((int)this.curEntry.Crc);
				if (this.curEntry.LocalHeaderRequiresZip64)
				{
					if (this.sizePatchPos == -1L)
					{
						throw new ZipException("Entry requires zip64 but this has been turned off");
					}
					this.baseOutputStream_.Seek(this.sizePatchPos, 0);
					this.WriteLeLong(this.curEntry.Size);
					this.WriteLeLong(this.curEntry.CompressedSize);
				}
				else
				{
					this.WriteLeInt((int)this.curEntry.CompressedSize);
					this.WriteLeInt((int)this.curEntry.Size);
				}
				this.baseOutputStream_.Seek(position, 0);
			}
			if ((this.curEntry.Flags & 8) != 0)
			{
				this.WriteLeInt(134695760);
				this.WriteLeInt((int)this.curEntry.Crc);
				if (this.curEntry.LocalHeaderRequiresZip64)
				{
					this.WriteLeLong(this.curEntry.CompressedSize);
					this.WriteLeLong(this.curEntry.Size);
					this.offset += 24L;
				}
				else
				{
					this.WriteLeInt((int)this.curEntry.CompressedSize);
					this.WriteLeInt((int)this.curEntry.Size);
					this.offset += 16L;
				}
			}
			this.entries.Add(this.curEntry);
			this.curEntry = null;
		}
		private void WriteEncryptionHeader(long crcValue)
		{
			this.offset += 12L;
			base.InitializePassword(base.Password);
			byte[] array = new byte[12];
			Random random = new Random();
			random.NextBytes(array);
			array[11] = (byte)(crcValue >> 24);
			base.EncryptBlock(array, 0, array.Length);
			this.baseOutputStream_.Write(array, 0, array.Length);
		}
		public override void Write(byte[] buffer, int offset, int count)
		{
			if (this.curEntry == null)
			{
				throw new InvalidOperationException("No open entry.");
			}
			if (buffer == null)
			{
				throw new ArgumentNullException("buffer");
			}
			if (offset < 0)
			{
				throw new ArgumentOutOfRangeException("offset", "Cannot be negative");
			}
			if (count < 0)
			{
				throw new ArgumentOutOfRangeException("count", "Cannot be negative");
			}
			if (buffer.Length - offset < count)
			{
				throw new ArgumentException("Invalid offset/count combination");
			}
			this.crc32_0.Update(buffer, offset, count);
			this.size += (long)count;
			CompressionMethod compressionMethod = this.curMethod;
			if (compressionMethod != CompressionMethod.Stored)
			{
				if (compressionMethod == CompressionMethod.Deflated)
				{
					base.Write(buffer, offset, count);
				}
			}
			else
			{
				if (base.Password != null)
				{
					this.CopyAndEncrypt(buffer, offset, count);
				}
				else
				{
					this.baseOutputStream_.Write(buffer, offset, count);
				}
			}
		}
		private void CopyAndEncrypt(byte[] buffer, int offset, int count)
		{
			byte[] array = new byte[4096];
			while (count > 0)
			{
				int num = (count < 4096) ? count : 4096;
				Array.Copy(buffer, offset, array, 0, num);
				base.EncryptBlock(array, 0, num);
				this.baseOutputStream_.Write(array, 0, num);
				count -= num;
				offset += num;
			}
		}
		public override void Finish()
		{
			if (this.entries != null)
			{
				if (this.curEntry != null)
				{
					this.CloseEntry();
				}
				long noOfEntries = (long)this.entries.get_Count();
				long num = 0L;
				using (List<ZipEntry>.Enumerator enumerator = this.entries.GetEnumerator())
				{
					while (enumerator.MoveNext())
					{
						ZipEntry current = enumerator.get_Current();
						this.WriteLeInt(33639248);
						this.WriteLeShort(51);
						this.WriteLeShort(current.Version);
						this.WriteLeShort(current.Flags);
						this.WriteLeShort((int)((short)current.CompressionMethodForHeader));
						this.WriteLeInt((int)current.DosTime);
						this.WriteLeInt((int)current.Crc);
						if (current.IsZip64Forced() || current.CompressedSize >= (long)((ulong)-1))
						{
							this.WriteLeInt(-1);
						}
						else
						{
							this.WriteLeInt((int)current.CompressedSize);
						}
						if (current.IsZip64Forced() || current.Size >= (long)((ulong)-1))
						{
							this.WriteLeInt(-1);
						}
						else
						{
							this.WriteLeInt((int)current.Size);
						}
						byte[] array = ZipConstants.ConvertToArray(current.Flags, current.Name);
						if (array.Length > 65535)
						{
							throw new ZipException("Name too long.");
						}
						ZipExtraData zipExtraData = new ZipExtraData(current.ExtraData);
						if (current.CentralHeaderRequiresZip64)
						{
							zipExtraData.StartNewEntry();
							if (current.IsZip64Forced() || current.Size >= (long)((ulong)-1))
							{
								zipExtraData.AddLeLong(current.Size);
							}
							if (current.IsZip64Forced() || current.CompressedSize >= (long)((ulong)-1))
							{
								zipExtraData.AddLeLong(current.CompressedSize);
							}
							if (current.Offset >= (long)((ulong)-1))
							{
								zipExtraData.AddLeLong(current.Offset);
							}
							zipExtraData.AddNewEntry(1);
						}
						else
						{
							zipExtraData.Delete(1);
						}
						byte[] entryData = zipExtraData.GetEntryData();
						byte[] array2 = (current.Comment != null) ? ZipConstants.ConvertToArray(current.Flags, current.Comment) : new byte[0];
						if (array2.Length > 65535)
						{
							throw new ZipException("Comment too long.");
						}
						this.WriteLeShort(array.Length);
						this.WriteLeShort(entryData.Length);
						this.WriteLeShort(array2.Length);
						this.WriteLeShort(0);
						this.WriteLeShort(0);
						if (current.ExternalFileAttributes != -1)
						{
							this.WriteLeInt(current.ExternalFileAttributes);
						}
						else
						{
							if (current.IsDirectory)
							{
								this.WriteLeInt(16);
							}
							else
							{
								this.WriteLeInt(0);
							}
						}
						if (current.Offset >= (long)((ulong)-1))
						{
							this.WriteLeInt(-1);
						}
						else
						{
							this.WriteLeInt((int)current.Offset);
						}
						if (array.Length > 0)
						{
							this.baseOutputStream_.Write(array, 0, array.Length);
						}
						if (entryData.Length > 0)
						{
							this.baseOutputStream_.Write(entryData, 0, entryData.Length);
						}
						if (array2.Length > 0)
						{
							this.baseOutputStream_.Write(array2, 0, array2.Length);
						}
						num += (long)(46 + array.Length + entryData.Length + array2.Length);
					}
				}
				using (ZipHelperStream zipHelperStream = new ZipHelperStream(this.baseOutputStream_))
				{
					zipHelperStream.WriteEndOfCentralDirectory(noOfEntries, num, this.offset, this.zipComment);
				}
				this.entries = null;
			}
		}
	}
}
